package com.gbei.center.utils;

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.UnsupportedEncodingException;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.TreeMap;

/**
 * RSA算法签名
 *
 * @author lixuzhou@foxmail.com
 * @version 0.1 2018/11/1
 */
public class GbeiRsaSignature {

  @SuppressWarnings("unused")
  private static Logger LOGGER = LoggerFactory.getLogger(GbeiRsaSignature.class);

  /**
   * 签名参数名
   */
  public static final String KEY_SIGN = "sign";

  /**
   * 编码格式参数名
   */
  public static final String KEY_CHARSET = "charset";
  /**
   * 默认编码格式
   */
  public static final String DEFAULT_CHARSET = "UTF-8";

  /**
   * 签名算法参数名
   */
  public static final String KEY_SIGN_TYPE = "signType";
  /**
   * 默认签名算法
   */
  public static final String DEFAULT_SIGN_TYPE = "RSA2";

  public static final String SIGN_TYPE_RSA = "RSA";
  private static final String SIGN_ALGORITHMS = "SHA1WithRSA";

  public static final String SIGN_TYPE_RSA2 = "RSA2";
  private static final String SIGN_SHA256RSA_ALGORITHMS = "SHA256WithRSA";
  /**
   * 数字签名排除订单的商品
   */
  public static final String KEY_PRODUCTS = "products";

  /**
   * 参数转换成待签字符串
   *
   * @param reqParams
   * @return
   */
  public static String getSignContent(Map<String, String> reqParams, String appSecret) {
    Map<String, String> sortedParams = new TreeMap<>();
    sortedParams.putAll(reqParams);

    StringBuffer content = new StringBuffer();
    Iterator<Map.Entry<String, String>> it = sortedParams.entrySet().iterator();
    Map.Entry<String, String> entry;
    String k;
    String v;
    content.append("AppSecret=").append(appSecret);
    while (it.hasNext()) {
      entry = it.next();
      k = entry.getKey();
      v = entry.getValue();      
      if (StringUtils.isNotEmpty(k) && StringUtils.isNotEmpty(v) && !k.equals(KEY_SIGN) && !k.equals(KEY_PRODUCTS)) {
        content.append("&" + k + "=" + v);
      }
    }
    return content.toString();
  }

  /**
   * 内容签名
   *
   * @param content
   * @param privateKey
   * @param charset
   * @param signType
   * @return
   * @throws Exception
   */
  public static String getSign(String content, String privateKey, String charset, String signType) throws Exception {
    if (SIGN_TYPE_RSA.equals(signType)) {
      return rsaSign(content, privateKey, charset);
    } else if (SIGN_TYPE_RSA2.equals(signType)) {
      return rsa256Sign(content, privateKey, charset);
    } else {
      throw new Exception("Sign Type is Not Support : signType=" + signType);
    }
  }

  /**
   * rsa内容验签
   *
   * @param content
   * @param sign
   * @param publicKey
   * @param charset
   * @param signType
   * @return
   * @throws Exception
   */
  public static boolean signCheck(String content, String sign, String publicKey, String charset, String signType)
    throws Exception {
    if (SIGN_TYPE_RSA2.equals(signType)) {
      return rsa256CheckContent(content, sign, publicKey, charset);
    } else {
      throw new Exception("Sign Type is Not Support : signType=" + signType);
    }
  }

  /**
   * rsa内容验签
   *
   * @param reqParams
   * @param publicKey
   * @return
   * @throws Exception
   */
  public static boolean signCheck(Map<String, String> reqParams, String publicKey, String appSecret) throws Exception {
    if (StringUtils.isEmpty(reqParams.get(KEY_SIGN))) {
      throw new IllegalArgumentException("sign is required.");
    }
    String signContent = getSignContent(reqParams, appSecret);
    return signCheck(signContent, reqParams.get(KEY_SIGN), publicKey, reqParams.get(KEY_CHARSET), reqParams.get(KEY_SIGN_TYPE));
  }

  /**
   * sha1WithRsa 加签
   *
   * @param content
   * @param privateKey
   * @param charset
   * @return
   * @throws Exception
   */
  private static String rsaSign(String content, String privateKey, String charset) throws Exception {
    try {
      return rsaSignCommon(content, privateKey, charset, SIGN_ALGORITHMS);
    }
    catch (InvalidKeySpecException ie) {
      throw new Exception("RSA私钥格式不正确，请检查是否正确配置了PKCS8格式的私钥", ie);
    }
    catch (Exception e) {
      throw new Exception("RSAcontent = " + content + "; charset = " + charset, e);
    }
  }

  /**
   * sha256WithRsa 加签
   *
   * @param content
   * @param privateKey
   * @param charset
   * @return
   * @throws Exception
   */
  private static String rsa256Sign(String content, String privateKey, String charset) throws Exception {
    try {
      return rsaSignCommon(content, privateKey, charset, SIGN_SHA256RSA_ALGORITHMS);
    }
    catch (Exception e) {
      throw new Exception("RSAcontent = " + content + "; charset = " + charset, e);
    }
  }

  private static String rsaSignCommon(String content, String privateKey, String charset, String signAlgorithms)
    throws Exception {
    PrivateKey priKey = getPrivateKeyFromPKCS8(SIGN_TYPE_RSA, privateKey);
    Signature signature = Signature.getInstance(signAlgorithms);
    signature.initSign(priKey);
    if (StringUtils.isEmpty(charset)) {
      signature.update(content.getBytes());
    } else {
      signature.update(content.getBytes(charset));
    }
    byte[] signed = signature.sign();
    return new String(Base64.encodeBase64(signed));
  }

  private static boolean rsaCheckContent(String content, String sign, String publicKey, String charset)
    throws Exception {
    return rsaCheckContentCommon(content, sign, publicKey, charset, SIGN_ALGORITHMS);
  }

  private static boolean rsa256CheckContent(String content, String sign, String publicKey, String charset)
    throws Exception {
    return rsaCheckContentCommon(content, sign, publicKey, charset, SIGN_SHA256RSA_ALGORITHMS);
  }

  private static boolean rsaCheckContentCommon(String content, String sign, String publicKey, String charset,
                                               String signAlgorithms) throws Exception {
    try {
      PublicKey pubKey = getPublicKeyFromX509("RSA", publicKey);
      Signature signature = Signature.getInstance(signAlgorithms);
      signature.initVerify(pubKey);
      if (StringUtils.isEmpty(charset)) {
        signature.update(content.getBytes());
      } else {
        signature.update(content.getBytes(charset));
      }
      return signature.verify(Base64.decodeBase64(sign.getBytes()));
    }
    catch (Exception e) {
      throw new Exception("RSAcontent = " + content + ",sign=" + sign + ",charset = " + charset, e);
    }
  }

  private static PrivateKey getPrivateKeyFromPKCS8(String algorithm, String privateKey) throws Exception {
    if (StringUtils.isEmpty(privateKey) || StringUtils.isEmpty(algorithm)) {
      return null;
    }
    KeyFactory keyFactory = KeyFactory.getInstance(algorithm);
    byte[] encodedKey = privateKey.getBytes();
    encodedKey = Base64.decodeBase64(encodedKey);
    return keyFactory.generatePrivate(new PKCS8EncodedKeySpec(encodedKey));
  }

  private static PublicKey getPublicKeyFromX509(String algorithm, String publicKey) throws Exception {
    KeyFactory keyFactory = KeyFactory.getInstance(algorithm);
    byte[] encodedKey = publicKey.getBytes();
    encodedKey = Base64.decodeBase64(encodedKey);
    return keyFactory.generatePublic(new X509EncodedKeySpec(encodedKey));
  }

  /**
   * md5
   * */
  public static String md5( String data)throws UnsupportedEncodingException {
    return DigestUtils.md5Hex(data.getBytes("UTF-8"));
  }

  /**
   * 校验md5
   * @param reqParams   参数集合
   * @param appSecret   应用密钥
   * @return
   */
  public static boolean checkMd5( Map<String, String> reqParams, String appSecret)throws UnsupportedEncodingException{
    if (StringUtils.isEmpty(reqParams.get(KEY_SIGN))) {
      throw new IllegalArgumentException("sign is required.");
    }
    String encryptedStr = reqParams.get("sign");
    String signContent = getSignContent(reqParams, appSecret);
    String newEncryptedStr = md5(signContent);
    return newEncryptedStr.equals(encryptedStr);
  }

  public static void main(String[] args) throws Exception {

    //例子生成sign   user/getInfo 的请求参数
    String privateKey =
            "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDIWWbl82dFGclnXbepvcsEg9XQhURMOms4RKehcxUZYyPa7/yDF+Kh8KB"
                    + "/lKmy7fZJlRmYLe5tCUMoDgEwojuM1JauxCkwlAL3L1yr3wWtzmVrXUvWM7mzpWuIezeUk9kszv3Dv6l"
                    + "/WjjF8uveXb1KcO2HHHUYuU3cBAIUOG3PwfEtBCkxLfnyjv1JKljsL0w"
                    +
                    "+N1Nd5epNpwGIqcXNLAib1zXcIlJNfqCW7s90QBB8zQ1kSmOHTG1UYy2J7eRzNqInN022aZRe1iE1bR53NJNZwL1O29rViZE9MS8bMv7N13vnrU/a6l+HWQ/f+HPH5AxQxx/ORWEA6NTPARDlmB6dAgMBAAECggEBAKJGF1wFTA3iFwlMFSi8+yULCzG731fGsXV/1i6koeBQNohsfSBJRl1YIwdzCAhPSX7L71EdUtKp/+T/guM7Y/RrdCjV5W7dU5GWoP7swxLF68uMY5J5jt39DB4dfyUuo2bxuQTFxqnKD9X1LiqLznfvA9S9rfJ6lJ+x92UdV4Q/52ge7rDPvSrvcD7mLZVih33EOntgHxS3hPMwY7qvL7oh48O6ExlpcXZ4Rkv1TVdM6gk3dUymAmEqC3c+/9idsHydAdlZ7D16WdrQkqXOzIYEIhkAP6kmJiZNqJjMUJh39TOZ6Fc4jpVyFTRqmCKLI7/XrwtzA6R3vt+hZnUSeRkCgYEA+SPcHdq7J27hGBn0v17Lwez5WxfZGZh0kHLIr0hjd2h4TrZaiAhMaZ/YVTc3+wO4w8MdBVrCWksLmxlDYEFcAyfjFImTKVJViq1PSAjZVuuj2F3JK8xcReqGiYjdlVYsNzi/aSgWvYQM4k+L+zFVSSHSNGYbmfC4gXfvb+oOBSMCgYEAzd2f9lm05M788ZY6/UJsfyf4dC5ZKCZCFpkZZMlPXEULBFOxjakmPZAnHLl/mqUUoN9B/zu3CVjHrZhxEotRXGdZF5GTZ0y1kGa2WDCFpUDENCxy3+mKbyJh4wLODPRBtcTMwnlzgdhkMSjVHx78iTMqNNeHgcDlfVlc+BQo6T8CgYBYHoLgXFkN3X3oFGYoudK/yJ15xrmXAAFahf7NYIEt3tmOnZjvvF+qjErr+RfnxK05MG23Ux6i6SA1v33BMdzlkNoUjYRrfR+caSg9hkO5JqdRA/r/bszEPetQAgS9qTkCXyP0gaVpaqdT9GbCdfSAZWH//o2MqUPZJx6dKt/CmQKBgFIbsgBUuTSFZheJPs/iXVm9/HeCQOclkzhmKd2r+S4pD4LTSr7glzL4utDXH5DzhK3BPhZpn9Bni4Suh3LJBFNhmQQweOU7kDMe9F1qRPOrSFYy4EJ0aFV5Fiev57T8+vRfoPdz72D7iMKesZovBrRGJvBagl57LEz5hn4ftE2LAoGANPn/PPMCiVCKdD0brinbi4XO2etINk4bFyqtyLnccWeqfdlHXpzAm2xohQlNdC+PRuuQ6tGdBRp4kraWcnGiyY+pfnJdWVmmL/sBeCRwHtm0kKh60ssJmMSqas8d3DUW6QaBJsFVEqbM8+IRlA3Onc/zGtLKKpU6M/1qxlqeINM=";
    final String CHARSET = "UTF-8";
    final String SIGNTYPE = "RSA2";

    //这个token对应的appSecret
    String appSecret = "tur2rlFfywR9OOP3fB5ZbsLTnNuNabI3";

    Map<String, String> reqParams = new HashMap<>();
    //请求公共参数
    reqParams.put("signType", "RSA2");
    reqParams.put("timestamp", "1558692273133");
    reqParams.put("token", "gb.b0b237729bbcffe7fe340e9076597f7b0bab608d.1559655708");
    reqParams.put("charset", "UTF-8");

    //会员接口参数
    reqParams.put("userMobile","13605327408");
    reqParams.put("extraInfo","{\"userName\":\"\",\"userInviteMobile\":\"\",\"userNickName\":\"\",\"userHeadFile\":\"\",\"subsystemString\":\"你好\"}");

    //行政区域编码接口
    //reqParams.put("areaName","江西省");
    //reqParams.put("extraInfo","{\"areaNext\":1}");

    //生成sign
    String sign = getSign(getSignContent(reqParams, appSecret), privateKey, CHARSET, SIGNTYPE);
    reqParams.put("sign",sign);
    LOGGER.info("sign = " + sign);


    String publicKey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyFlm5fNnRRnJZ123qb3LBIPV0IVETDprOESnoXMVGWMj2u"
            + "/8gxfiofCgf5Spsu32SZUZmC3ubQlDKA4BMKI7jNSWrsQpMJQC9y9cq98Frc5la11L1jO5s6VriHs3lJPZLM79w7"
            +
            "+pf1o4xfLr3l29SnDthxx1GLlN3AQCFDhtz8HxLQQpMS358o79SSpY7C9MPjdTXeXqTacBiKnFzSwIm9c13CJSTX6glu7PdEAQfM0NZEpjh0xtVGMtie3kczaiJzdNtmmUXtYhNW0edzSTWcC9Ttva1YmRPTEvGzL+zdd7561P2upfh1kP3/hzx+QMUMcfzkVhAOjUzwEQ5ZgenQIDAQAB";
    LOGGER.info("publicKey = " + publicKey);


   // LOGGER.info("result =  " + GbeiRsaSignature.signCheck(reqParams, publicKey, appSecret));


    String md5 = md5(getSignContent(reqParams, appSecret));
    reqParams.put("sign",md5);
    LOGGER.info("md5 =" + md5);

    boolean r = checkMd5(reqParams, appSecret);

    LOGGER.info("r =" + r);
  }
}
